package com.zxjbyte.yiyi.framework.oss.factory;

import cn.hutool.core.util.ReflectUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.zxjbyte.yiyi.framework.common.constant.RedisKeyDefine;
import com.zxjbyte.yiyi.framework.common.util.CollXUtil;
import com.zxjbyte.yiyi.framework.common.util.JsonUtil;
import com.zxjbyte.yiyi.framework.common.util.ValidationUtil;
import com.zxjbyte.yiyi.framework.oss.config.OssProperties;
import com.zxjbyte.yiyi.framework.oss.OssConfigCacheObject;
import com.zxjbyte.yiyi.framework.oss.enums.OssStorageEnum;
import com.zxjbyte.yiyi.framework.oss.storage.AbstractOssConfigWrapper;
import com.zxjbyte.yiyi.framework.oss.storage.OssConfig;
import com.zxjbyte.yiyi.framework.oss.storage.OssOperations;
import com.zxjbyte.yiyi.framework.oss.storage.db.DbOssConfig;
import com.zxjbyte.yiyi.framework.oss.storage.db.DbOssConfigWrapper;
import com.zxjbyte.yiyi.framework.oss.storage.local.LocalOssConfig;
import com.zxjbyte.yiyi.framework.oss.storage.local.LocalOssConfigWrapper;
import com.zxjbyte.yiyi.framework.oss.storage.s3.S3OssConfig;
import com.zxjbyte.yiyi.framework.oss.storage.s3.S3OssConfigWrapper;
import com.zxjbyte.yiyi.framework.redis.manager.RedisDao;
import com.zxjbyte.yiyi.module.system.api.oss.OssConfigApi;
import com.zxjbyte.yiyi.module.system.domain.result.OssConfigResult;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;

/**
 * 默认oss配置工厂类
 *
 * @Author zhangxingjia
 * @Date 2023/5/15 14:43
 * @Version: 1.0
 */
@RequiredArgsConstructor
public class DefaultOssConfigFactory implements OssConfigFactory {

    private final OssProperties properties;
    private final OssConfigApi ossConfigApi;
    private final RedisDao redisDao;

    @Override
    public S3OssConfigWrapper getS3OssConfig(){
        return (S3OssConfigWrapper) buildWrapper(OssStorageEnum.S3);
    }

    @Override
    public LocalOssConfigWrapper getLocalOssConfig(){
        return (LocalOssConfigWrapper) buildWrapper(OssStorageEnum.LOCAL);
    }

    @Override
    public DbOssConfigWrapper getDbOssConfig(){
        return (DbOssConfigWrapper) buildWrapper(OssStorageEnum.DB);
    }

    @Override
    public void reloadRedisCache(String storageCode) {
        OssConfigCacheObject ossConfigCacheObject = getDbMasterOssConfig(storageCode);
        redisDao.set(RedisKeyDefine.module(RedisKeyDefine.Modules.OSS_CONFIG).buildKey(storageCode), ossConfigCacheObject);
        // 重新初始化模板类init方法
        Class<? extends OssOperations> templateClass = OssStorageEnum.getByStorageCode(storageCode).getTemplateClass();
        initMethodInvoke(templateClass);
    }

    @SneakyThrows
    private <T> void initMethodInvoke(Class<T> clazz){
        T bean = SpringUtil.getBean(clazz);
        Method initMethod = ReflectUtil.getMethodByName(bean.getClass(), "init");
        // 设置 init 方法可访问
        initMethod.setAccessible(true);
        initMethod.invoke(bean);
    }


    private AbstractOssConfigWrapper buildWrapper(OssStorageEnum storageEnum){
        OssConfigInfo ossConfigInfo = getMasterOssConfig(storageEnum);
        switch (storageEnum) {
            case LOCAL:
                return createWrapper(ossConfigInfo.getConfig(), ossConfigInfo.getStorageCode(), LocalOssConfigWrapper::new);
            case DB:
                return createWrapper(ossConfigInfo.getConfig(), ossConfigInfo.getStorageCode(), DbOssConfigWrapper::new);
            case S3:
            default:
                return createWrapper(ossConfigInfo.getConfig(), ossConfigInfo.getStorageCode(), S3OssConfigWrapper::new);
        }
    }

    private <T extends AbstractOssConfigWrapper> T createWrapper(OssConfig config, String storageCode, Supplier<T> wrapperSupplier) {
        T wrapper = wrapperSupplier.get();
        wrapper.setOssConfig(config);
        wrapper.setStorageCode(storageCode);
        return wrapper;
    }

    private OssConfigInfo getMasterOssConfig(OssStorageEnum storageEnum) {
        return Optional.ofNullable(defaultOssConfig(storageEnum))
                .map(ossConfig -> new OssConfigInfo(ossConfig, properties.getStorage().getStorageCode()))
                .orElseGet(() -> {
                    OssConfigCacheObject cache = getOssConfigRedisCache(storageEnum.getStorageCode());
                    return new OssConfigInfo(cache.getConfig(), cache.getStorageCode());
                });
    }

    private OssConfigCacheObject getOssConfigRedisCache(String storageCode) {
        return redisDao.get(RedisKeyDefine.module(RedisKeyDefine.Modules.OSS_CONFIG).buildKey(storageCode), () -> getDbMasterOssConfig(storageCode));
    }

    private OssConfig defaultOssConfig(OssStorageEnum storageEnum) {
        // 默认优先获取yaml中的oss配置
        if (!properties.getEnabled()) {
            return null;
        }
        if(storageEnum == properties.getStorage()
                && CollXUtil.isNotEmpty(properties.getConfig())){
            Class<? extends OssConfig> configClass = properties.getStorage().getConfigClass();
            Map<String, Object> configMap = properties.getConfig();
            return JsonUtil.mapToObjectWithTypeInfo(configMap, configClass);
        }
        return null;
    }

    /**
     * 获取redis中缓存的oss配置信息
     * @param storageCode 储存器编号
     * @return redis缓存对象
     */
    private OssConfigCacheObject getDbMasterOssConfig(String storageCode) {
        OssConfigResult masterOssConfigResult = ossConfigApi.getMasterConfigByStorageCode(storageCode);
        ValidationUtil.assertNotNull(masterOssConfigResult, "StorageCode[" + storageCode + "], oss master configuration");
        // 封装需要缓存到redis中的信息
        OssConfigCacheObject cache = new OssConfigCacheObject();
        cache.setId(masterOssConfigResult.getId());
        cache.setStorageCode(masterOssConfigResult.getStorageCode());
        cache.setConfig((OssConfig) masterOssConfigResult.getConfig());
        return cache;
    }

    @Data
    @AllArgsConstructor
    public static class OssConfigInfo{
        private OssConfig config;
        private String storageCode;
    }
}
